package 
{
	import adobe.utils.MMExecute;
	
	import flash.display.DisplayObject;
	import flash.display.MovieClip;
	import flash.events.*;
	import flash.geom.Point;
	import flash.ui.Keyboard;
	import flash.ui.Mouse;
	import flash.utils.getQualifiedClassName;
	import flash.utils.getTimer;
		
	/** The highest level container for this project, a task wraps all items. */
	public class GeometryGameItem extends MovieClip
	{
		public var TYPES:Array;
		public var id:String;
		public var free_points:int; // num of free points (i.e. not between two already existing points)
		public var xml:XML;
		// constants
		// public variables
		public var MENU_HEIGHT:int = 50;
		public var GARDEN_TIME:int; // if -1 no garden, if 0 indefinite, if greater than 0 until user stops
		
		// private variables
		private var DRAWING_WIDTH:int;
		private var buildMode:Boolean;
		private var item_number:int;
		private var command_count:uint = 0;
		private var message_count:uint = 0;
		private var drawingVProp:Number,questionVProp:Number;
		private var initial_menu:String;
		private var currentTool:String = "";
		private var currentShape:String = "";
		private var previousCMD:String = "";
		private var previousStatus:String = "";
		private var previousParameters:Array;
		private var haltUntilErrorComplete:Boolean=false;
		private var itimestamp:uint = 0, timestamp:uint;
		// private objects
		private var parentPolygons:Array; //Array of animated shapes representing the "parents"
		private var hideToolArray:Array; //Array of tool names that are hidden
		private var targetMenu:TargetMenu;
		private var ipanel:ItemIntroPanel;
		private var dpanel:DrawingPanel;
		private var fbmenu:ForwardBackMenu;
		private var tMenu:ToolMenu = null;
		private var ssMenu:ShapeSelectionMenu = null;
		private var messageQueue:Array; // stores messages which will be triggered by command processing
		private var objectiveQueue:Array; // stores objectives which must be met before proceeding to a phase shift (i.e., tool-shape)
		private var actionQueue:Array;
		
		public function GeometryGameItem(xml:XML, item_number:int=-1, buildMode:Boolean=false)
		{
			this.xml = xml;
			// get thresholds, and multiplier for threshold to use tools
			if (xml.attribute("id").length() > 0){id = String(xml.attribute("id")[0]);}else {id = "i"+String(Math.floor(Math.random()*int.MAX_VALUE));}
			if (xml.attribute("initial_menu").length() > 0) {initial_menu = xml.attribute("initial_menu")[0];} else {initial_menu="tool";}
			if (xml.attribute("garden_time").length() > 0){GARDEN_TIME = int(xml.attribute("garden_time")[0]);} else {GARDEN_TIME = 0;}
			this.buildMode = buildMode;
			this.item_number = item_number;
			this.TYPES = ShapeProductionTask.TYPES;
			this.addEventListener(Event.ADDED, handleAdded);
		}
		private function handleAdded (event:Event):void
		{
			this.removeEventListener(Event.ADDED, handleAdded);
			this.graphics.drawRect(0,0,this.parent.width, this.parent.height);
						
			// setup duration
			timestamp = flash.utils.getTimer();
			if (xml..savestate.length() > 0)
			{
				itimestamp = uint(xml..savestate[xml..savestate.length()-1].attribute("timestamp"));
			}
			
			// how many free points can we draw, relevant to both tool menu and drawing menu
			// first check the last save state
			if (xml..drawstate.length()>0 && xml..drawstate[xml..drawstate.length()-1].attribute("free_points").length()>0){free_points = int(xml..drawstate[xml..drawstate.length()-1].attribute("free_points")[0]);} 
			else if (xml.attribute("free_points").length()>0){free_points = int(xml.attribute("free_points")[0]);} 
			else if (xml..initial_state.attribute("free_points").length()>0){free_points = int(xml..initial_state.attribute("free_points")[0]);} 
			else {free_points = -1;}
			
			// add free points to drawspace
			if (xml..initial_state.length()>0)
			{
				if (xml..initial_state[0]..drawstate.length()==0)
				{
					var node:XML = new XML();
					node = <drawstate />;
					node.@free_points = free_points;
					xml..initial_state[0]..appendChild(node);
				} else {
					xml..initial_state[0]..drawstate[0].@free_points = free_points;	
				}
			}else{				
			}
			
			// Add an triggered messages onto the queue
			messageQueue = new Array(); 
			objectiveQueue = new Array();
			actionQueue = new Array();
			var mid:String="", message:String="", trigger_cmd:String="", trigger_status:String="", trigger_id:String="", repeat_count:uint=0, delay_count:uint=0, message_type:String=MessageBox.EPHEMERAL_TYPE, mx:Number=this.width/3, my:Number=this.height/3;
			for each (var m:XML in xml..message)
			{
				if (m.attribute("id").length()>0){ mid = m.attribute("id")[0]; } else { mid="";}
				if (m.attribute("trigger_cmd").length()>0){ trigger_cmd = m.attribute("trigger_cmd")[0]; } else {trigger_cmd="";}
				if (m.attribute("trigger_status").length()>0){ trigger_status = m.attribute("trigger_status")[0]; } else {trigger_status="";}
				if (m.attribute("trigger_id").length()>0){ trigger_id = m.attribute("trigger_id")[0]; } else {trigger_id="";}
				if (m.attribute("repeat_count").length()>0){ repeat_count = uint(m.attribute("repeat_count")[0]); } else {repeat_count=0;}
				if (m.attribute("delay_count").length()>0){ delay_count = uint(m.attribute("delay_count")[0]); } else {delay_count=0;}
				if (m.attribute("message_type").length()>0){ message_type = m.attribute("message_type")[0]; } else {message_type=MessageBox.EPHEMERAL_TYPE;}
				if (m.attribute("x").length()>0){ mx = Number(m.attribute("x")[0]); } else {mx=this.width/3}
				if (m.attribute("y").length()>0){ my = Number(m.attribute("y")[0]); } else {my=this.height/3}
				if (mx >= 1 || my >= 1){ mx = mx; } else {mx = this.width * mx}
				if (my >= 1 || my >= 1){ my = my; } else {my = this.height * my}
				addToMessageQueue(mid, m.toString(), trigger_cmd, trigger_status, trigger_id, repeat_count, delay_count, message_type, mx, my);
			}
			var objid:String="", message_id:String="";
			// don't put objectives on queue if we have already begun this item
			if (xml..drawstate.length() < 2)
			{
				for each (var obj:XML in xml..objective)
				{
					//trace("objective", obj.attributes().toString());
					if (obj.attribute("id").length()>0){ objid = obj.attribute("id")[0];} else {objid="";}
					if (obj.attribute("trigger_cmd").length()>0){ trigger_cmd = obj.attribute("trigger_cmd")[0]; } else {trigger_cmd="";}
					if (obj.attribute("trigger_status").length()>0){ trigger_status = obj.attribute("trigger_status")[0]; } else {trigger_status="";}
					if (obj.attribute("trigger_id").length()>0){ trigger_id = obj.attribute("trigger_id")[0]; } else {trigger_id="";}
					if (obj.attribute("repeat_count").length()>0){ repeat_count = uint(obj.attribute("repeat_count")[0]); } else {repeat_count=0;}
					if (obj.attribute("delay_count").length()>0){ delay_count = uint(obj.attribute("delay_count")[0]); } else {delay_count=0;}
					//trace("delay count", delay_count);
					addToObjectiveQueue(objid, trigger_cmd, trigger_status, trigger_id, repeat_count, delay_count );
				}
			}
			if (xml..hide_tools.length()>0)
			{
				hideToolArray = new Array();
				for each (var h:XML in xml..hide_tools[0]..tool)
				{
					hideToolArray.push(h.toString());
				}
			} else
			{
				hideToolArray = null;
			}
			tMenu = new ToolMenu(192*ShapeProductionTask.unit, this.height);
			DRAWING_WIDTH = this.width - tMenu.x - tMenu.WIDTH;
			
			// get an array of the "parentPolygons"
			parentPolygons = new Array();
			// are we restoring old parent polygons or creating new ones?
			var t:XML, type:String, id:String, color:uint, draw_as:String;
			if (xml..parent_shapes.length() > 0 && xml..parent_shape.length() > 0)
			{       // old parent polygon
					for each (t in xml..parent_shapes[0]..parent_shape)
					{
						if (t.attribute("type").length() > 0){ type = t.attribute("type")[0]; } else { type="";}
						if (t.attribute("id").length() > 0){ id = t.attribute("id")[0];} else { id="";}
						if (t.attribute("color").length() > 0){ color = uint(t.attribute("color")[0]);} else { color=0x000000;}
						if (t.attribute("draw_as").length() > 0){ draw_as = t.attribute("draw_as")[0];} else { draw_as="";}
						var ppoints:Array = new Array();
						for each (var p:XML in t..point)
						{
							ppoints.push(new Point(Number(p.@x), Number(p.@y)));
						}
						parentPolygons.push(new ParentPolygon(DRAWING_WIDTH/4, DRAWING_WIDTH/4, type, id, TYPES, ppoints, color, draw_as));
					}
			} else  // new parent polygons
			{
				if (xml..targets.length()>0)
				{
					for each (t in xml..targets[0]..target)
					{
						if (t.attribute("type").length() > 0){ type = t.attribute("type")[0]; } else { type="";}
						if (t.attribute("id").length() > 0){ id = t.attribute("id")[0];} else { id="";}
						if (t.attribute("draw_as").length() > 0){ draw_as = t.attribute("draw_as")[0];} else { draw_as="";}
						
						parentPolygons.push(new ParentPolygon(DRAWING_WIDTH/4, DRAWING_WIDTH/4, type, id, TYPES, null, 0x000000, draw_as));
					}		
				}			
			}
			// the introduction panel goes where the drawing panel will go until it is completed
			ipanel = new ItemIntroPanel(DRAWING_WIDTH, this.height, MENU_HEIGHT, xml, parentPolygons);
			ipanel.x = this.width - DRAWING_WIDTH - 10;
			//ipanel.y = targetMenu.height;
			ipanel.addEventListener(Event.COMPLETE, handleIntroComplete);
			this.addChild(ipanel);
			//drawingVProp = (this.height-targetMenu.height-MENU_HEIGHT)/this.height;
			//newMessage("Abcdefghijklmnopqrstuvwxyz", 100, 100, MessageBox.EPHEMERAL_TYPE);
		}
		
		private function handleIntroComplete (event:Event):void
		{
			ipanel.removeEventListener(Event.COMPLETE, handleIntroComplete);
			this.removeChild(ipanel);
			
			// save this group of parent shapes to the xml, but only if new
			if (xml..parent_shapes.length() == 0)
			{
				var node:XML = new XML();
				node = <parent_shapes> </parent_shapes>;
				for each (var pp:ParentPolygon in parentPolygons)
				{
					var ppnode:XML = new XML();
					ppnode = <parent_shape> </parent_shape>;
					ppnode.@id = String(pp.id);
					ppnode.@type = pp.type;
					ppnode.@color = pp.color;
					if (pp.type != pp.draw_as) ppnode.@draw_as = pp.draw_as;
					var pnode:XML;
					var cpoints:Array = pp.minimizedPoints;
					for each (var p:Point in cpoints)
					{
						pnode = new XML;
						pnode = <point/>;
						pnode.@x = p.x;
						pnode.@y = p.y;
						ppnode.appendChild(pnode);
					}
					node.appendChild(ppnode);
				}
				xml.appendChild(node);
				ShapeProductionTask(parent.parent.parent).saveXML();
			}
			
			// add target(goal) menu
			targetMenu = new TargetMenu(DRAWING_WIDTH, parentPolygons, TYPES);
			targetMenu.x = this.width - DRAWING_WIDTH;
			targetMenu.addEventListener(Event.COMPLETE, handleTargetsComplete);
			this.addChild(targetMenu);
			targetMenu.uncover();
			
			//drawingVProp = (this.height-targetMenu.height-MENU_HEIGHT)/this.height;
			//trace("drawing height", this.width, this.height, question.width, question.height, drawingVProp);
			dpanel = new DrawingPanel(DRAWING_WIDTH, this.height-targetMenu.height, xml);
			dpanel.x = this.width - DRAWING_WIDTH;
			dpanel.y = targetMenu.height;
			//dpanel.addEventListener(MouseEvent.MOUSE_OVER, handleMouseOverPanel);
			this.addChild(dpanel);
			
			if (initial_menu == "tool")
			{
				processCommand("toolMenu", "initiate", this);
			}
			else if (initial_menu == "shapeSelection")
			{
				processCommand("shapeSelectionMenu", "initiate", this);				
			}
			// Whether we can immediately skip to next (or go to previous) is determined by "buildMode"
			if (buildMode)
			{
				fbmenu = new ForwardBackMenu (dpanel.width, MENU_HEIGHT, "Next item", true, "Previous item", true);
				fbmenu.x = dpanel.x;
				fbmenu.y = dpanel.y + dpanel.height;
				fbmenu.addEventListener(Event.COMPLETE, handleMenuComplete);
				fbmenu.addEventListener(Event.CANCEL, handleMenuCanceled);			
				this.addChild(fbmenu);
			}
			
			this.stage.addEventListener(KeyboardEvent.KEY_DOWN, handleKeyboard);
			
		}
		
		/// EVENTS FOR COMPLETING THIS ITEM
		/** handles a completed event by the target menu.  Indicates that all targets have been met */
		private function handleTargetsComplete(event:Event):void
		{
			if (!haltUntilErrorComplete)completeItem();			
		}
		/** The item is complete, iterate and try another */
		private function handleMenuComplete(event:Event):void
		{
			if (!haltUntilErrorComplete) completeItem();
		}
		/** The item is complete, iterate and try another */
		private function handleMenuCanceled(event:Event):void
		{
			cancelItem();			
		}
		public function completeItem():void
		{
			targetMenu.removeEventListener(Event.COMPLETE, handleTargetsComplete);
			if (fbmenu != null)
			{
				fbmenu.removeEventListener(Event.COMPLETE, handleMenuComplete);
				fbmenu.removeEventListener(Event.CANCEL, handleMenuCanceled);
				this.removeChild(fbmenu);
			}
			this.removeChild(dpanel);
			this.xml.@duration = flash.utils.getTimer() - timestamp + itimestamp;
			this.xml.@complete="true";
			//ShapeProductionTask(parent.parent.parent).saveXML();
			
			this.dispatchEvent(new Event(Event.COMPLETE));
		}
		public function cancelItem():void
		{
			targetMenu.removeEventListener(Event.COMPLETE, handleTargetsComplete);
			if (fbmenu != null)
			{
				fbmenu.removeEventListener(Event.CANCEL, handleMenuCanceled);
				fbmenu.removeEventListener(Event.COMPLETE, handleMenuComplete);
				this.removeChild(fbmenu);
			}
			this.removeChild(dpanel);
			this.dispatchEvent(new Event(Event.CANCEL));
		}
		/** Get first occurrence of parent polygon with the given shape type */
		public function getParentPolygonWithType(type:String):ParentPolygon
		{
			var pp:ParentPolygon;
			for each (pp in parentPolygons)
			{
				if (pp.type == type) return pp;
			}
			return null;
		}
		//////////////////////////////// Queued ACTIONS //////////////////////////////////////////
		public function addToActionQueue(aid:String, trigger_cmd:String, trigger_status:String, trigger_msg_id:String, trigger_type:String, repeat_count:uint, delay_count:uint):ActionOnQueue
		{
			var a:ActionOnQueue = new ActionOnQueue(aid, trigger_cmd, trigger_status, trigger_msg_id, trigger_type, repeat_count, delay_count);
			actionQueue.push(a);
			return(a);
		}
		public function correctActionTrigger(trigger_cmd:String, trigger_status:String, trigger_msg_id:String):Boolean
		{
			for (var i:uint=0; i < actionQueue.length; i++)
			{
				var a:ActionOnQueue = actionQueue[i];
				// there was a child error, user finished reading the error message, clear out the child shape
				if (trigger_cmd=="message" && trigger_status=="complete"&&a.trigger_msg_type=="child_error"&&trigger_msg_id==a.trigger_msg_id)
				{
					return true;	
				}					
			}
			return false;
		}
		/** Performs a specific action when triggered */
		public function triggerActionOnQueue(trigger_cmd:String, trigger_status:String, trigger_msg_id:String):Boolean
		{
			for (var i:uint=0; i < actionQueue.length; i++)
			{
				var a:ActionOnQueue = actionQueue[i];
				// there was a child error, user finished reading the error message, clear out the child shape
				if (trigger_cmd=="message" && trigger_status=="complete"&&a.trigger_msg_type=="child_error"&&trigger_msg_id==a.trigger_msg_id)
				{
					if (a.delay_count == 0)
					{
						if (ssMenu != null) ssMenu.disable = false;
						dpanel.clearChild();
						dpanel.setChildClosedPolygon(null);
						dpanel.resumeCursor();
						if (a.repeat_count > 0)
						{
							a.repeat_count--;
						} else
						{
							actionQueue.splice(i, 1);	
						}
						return true;
					} else
					{
						a.delay_count--;
						return false;
					}
				}					
			}
			return false;
		}
		//public function isActionOnQueueChildError(trigger_cmd:String, trigger_status:String, trigger_msg_id:String):Boolean
		/** If no actions on Queue return true, if there is an action on queue, make sure that the command is complete a child error message */
		public function isActionOnQueueChildError():Boolean
		{
			if (actionQueue.length > 0)
			{
				var a:ActionOnQueue = actionQueue[i];
				for (var i:uint=0; i < actionQueue.length; i++)
				{
					//if (trigger_cmd=="message" && trigger_status=="complete"&&a.trigger_msg_type=="child_error"&&trigger_msg_id==a.trigger_msg_id)
					if (a.trigger_cmd=="message" && a.trigger_status=="complete"&&a.trigger_msg_type=="child_error")
					{
						return true;
					} 
				}
				//trace("Complete error first");
				return false;
			} else
			{
				return true;
			}
		}
		//////////////////////////////// OBJECTIVES ////////////////////////////////////////////////
		/** Are there active objectives on queue */
		public function activeObjectives():Boolean
		{
			if (objectiveQueue.length == 0)
			{
				return false;
			} else
			{
				for each (var obj:ObjectiveOnQueue in objectiveQueue)
				{
					if (obj.currently_active) return true;
				}
				return false;
			}
		}
		public function criticalObjectives():Boolean
		{
			if (objectiveQueue.length == 0)
			{
				return false;
			} else
			{
				for each (var obj:ObjectiveOnQueue in objectiveQueue)
				{
					if (obj.currently_active && obj.isCritical) return true;
				}
				return false;
			}
		}
		/**
		 *  Adds a message with the current specs to the queue.
		 */
		public function addToObjectiveQueue (objid:String, trigger_cmd:String, trigger_status:String, trigger_msg_id:String, repeat_count:uint, delay_count:uint, isCritical:Boolean=false):void
		{
			var obj:ObjectiveOnQueue = new ObjectiveOnQueue(objid, trigger_cmd, trigger_status, trigger_msg_id, repeat_count, delay_count, isCritical);
			objectiveQueue.push(obj);
		}
		/** On every command check to see whether this command is an objective.  If this objective is to be repeated then simply disable, else remove from queue */ 
		public function disableObjectiveFromQueue(trigger_cmd:String, trigger_status:String, trigger_id:String):Boolean
		{
			for (var i:uint=0; i < objectiveQueue.length; i++)
			{
				var obj:ObjectiveOnQueue = objectiveQueue[i];
				//trace("objective", obj.currently_active, obj.trigger_cmd, obj.trigger_status, obj.current_delay_count, obj.delay_count);
				if (obj.currently_active && ((trigger_cmd=="message" && trigger_status=="complete" && obj.trigger_id == trigger_id) || ((obj.trigger_cmd == "" || obj.trigger_cmd == trigger_cmd) && (obj.trigger_status == "" || obj.trigger_status == trigger_status))))
				{
					if (obj.current_delay_count == obj.delay_count)
					{
						if (obj.current_repeat_count == obj.repeat_count)
						{
							objectiveQueue.splice(i, 1);	
						} else
						{
							obj.currently_active = false;
						}
						processCommand("objective", "complete", this, new Array(obj.id));						
						return true;
					} else
					{
						obj.current_delay_count++;
						return false;
					}					
				}
			}
			return false;
		}
		/**
		 *  Check to see if there is an objective on the queue, if so return true, else return false.  If objective is on queue, and a message is associated trigger that message 
		 */ 
		public function triggerObjectiveOnQueue():Boolean
		{
			for (var i:uint=0; i < objectiveQueue.length; i++)
			{
				var obj:ObjectiveOnQueue = objectiveQueue[i];
				//if (obj.currently_active &&((trigger_cmd=="message" && trigger_status=="complete" && obj.trigger_id == trigger_id) || ((obj.trigger_cmd == "" || obj.trigger_cmd == trigger_cmd) && (obj.trigger_status == "" || obj.trigger_status == trigger_status))))
				//{
					processCommand("objective", "initiate", this, new Array(obj.id));
					return true;
				//}					
			}
			return false;
		}
		
		//////////////////////////////// MESSAGES ////////////////////////////////////////////////
		/**
		 *  Adds a message with the current specs to the queue.
		 */
		public function addToMessageQueue (mid:String, message:String, trigger_cmd:String, trigger_status:String, trigger_id:String, repeat_count:uint, delay_count:uint, message_type:String, x:Number, y:Number):void
		{
			var m:MessageOnQueue = new MessageOnQueue(mid, message, trigger_cmd, trigger_status, trigger_id, repeat_count, delay_count, message_type, x, y);
			messageQueue.push(m);
		}
		
		/**
		 *  Check to see if there is a message, with the given triggers on the queue. Returns whether a message was triggered or not.
		 */ 
		public function triggerMessageOnQueue(trigger_cmd:String, trigger_status:String, trigger_id:String):Boolean
		{
			for (var i:uint=0; i < messageQueue.length; i++)
			{
				var m:MessageOnQueue = messageQueue[i];
				if ((trigger_cmd=="objective" && trigger_status=="initiate" && trigger_id==m.trigger_id) || (trigger_cmd=="message" && trigger_id==m.trigger_id) || ((m.trigger_cmd == "" || m.trigger_cmd == trigger_cmd) && (m.trigger_status == "" || m.trigger_status == trigger_status)))
				{
					if (m.delay_count == 0)
					{
						newMessage(this, m.message, m.x, m.y, m.message_type, m.id);
						if (m.repeat_count > 0)
						{
							m.repeat_count--;
						} else
						{
							messageQueue.splice(i, 1);	
						}
						return true;
					} else
					{
						m.delay_count--;
						return false;
					}
				}					
			}
			return false;
		}
		
		/** This function creates a new message but first checks to make sure that we are not completely overlapping a previous message */
		public function newMessage (callingObject:DisplayObject, message:String, initx:Number, inity:Number, mtype:String, mid:String="", MAX_WIDTH:Number=Number.MAX_VALUE, MAX_HEIGHT:Number=Number.MAX_VALUE):String
		{
			//trace("message x, y", initx, inity);
			// adjust initx and inity based on the position of the calling object
			var point:Point = callingObject.localToGlobal(new Point(initx, inity));
			if (point.x > this.width - 100) point.x = this.width - 100;
			if (point.y > this.height - 100) point.y = this.height - 100;
			// we don't want to place the message in the title area or tool area
			//if (inity < targetMenu.height) inity = targetMenu.height;
			//if (tMenu != null && initx < tMenu.width) initx = tMenu.width;
			// loop through all children, if they are messages check x,y
			message_count = 0;
			var isSet:Boolean = false;
			for (var i:int=0; i < this.numChildren; i++)
			{	
				if (flash.utils.getQualifiedClassName(this.getChildAt(i)) == "MessageBox")
				{
					message_count++;
					var m:MessageBox = MessageBox(this.getChildAt(i));
					if (Math.abs(m.x - point.x) < 10 && Math.abs(m.y - point.y) < 10 && !isSet)
					{
						point.x = m.x + MessageBox.TITLE_HEIGHT;
						point.y = m.y + MessageBox.TITLE_HEIGHT;
						isSet = true;
					}
				}
			}
			//trace("point of message", point);
			// adjust max width and max height so that they don't exceed the boundaries of the display
			var _MAX_WIDTH:Number = this.width - point.x;
			if (_MAX_WIDTH < MAX_WIDTH) MAX_WIDTH = _MAX_WIDTH;
			var _MAX_HEIGHT:Number = this.height - point.y;
			if (_MAX_HEIGHT < MAX_HEIGHT) MAX_HEIGHT = _MAX_HEIGHT;
			var mb:MessageBox = new MessageBox(message, point.x, point.y, mtype, mid, MAX_WIDTH, MAX_HEIGHT);
			this.addChildAt(mb, this.numChildren-message_count-1);			
			return (mb.id);
		}									
		
		
		////////////////////////////////////// 			PROCESS DATA 	/////////////////////////////////////////////////////////
		/** This function is called from a panel when a command is given 
		 *  cmd can be pen, parallel, etc., a state change name
		 *  status can be "initiate", "in_progress", "complete", or "error"
		 */		
		public function processCommand (cmd:String, status:String, callingObject:DisplayObject, parameters:Array=null):Boolean
		{
			var type:String, error:String;
			command_count++;
			var ts:uint = flash.utils.getTimer() - timestamp + itimestamp;
			trace("command:", cmd, status, ts, parameters);
			
			// put message into strings
			var messageID:String;
			if (parameters != null && parameters.length>0 && (cmd=="message"&&status=="complete" || cmd=="objective"&& (status=="initiate"||status=="complete"))){messageID=parameters[0];}else{messageID=""}
			
			/// Do not allow command if there is a critical objective and this ain't it
			if (this.criticalObjectives() && !this.correctActionTrigger(cmd, status, messageID)) return false;
			
			/////////////////////// XML ///////////////////////////////////
			// prepare save data
			var node:XML = new XML();
			node = 
				<savestate num={command_count} cmd={cmd} status={status} timestamp={ts}>
				</savestate>;
			
			if (parameters != null)
			{
				for (var i:uint=0; i < parameters.length; i++)
				{
					var pnode:XML = new XML();
					pnode = <parameter num={i} contents={parameters[i]} />
					node.appendChild(pnode);
				}
			}
			// PROCESS COMMAND WITH DRAWSTATE UPDATE
			if (status=="in_progress" && (cmd=="pen"||cmd=="delete"||cmd=="drag"||cmd=="pin"||cmd=="parallel"||cmd=="length"||cmd=="angle"||cmd=="right"))
			{
				if (cmd=="pen" || cmd=="delete")
				{
					// parameters include number of free points available
					if (parameters != null) 
					{
						free_points = int(parameters[1]);
						if (tMenu != null)
						{
							tMenu.freePointsRemaining = free_points;
						}
					}
				}
				dpanel.updateComponents();
				node = dpanel.appendNewDrawState(node, command_count, free_points);
				ShapeProductionTask(parent.parent.parent).saveXML();						
			} else if (status=="initiate")
			{
				switch (cmd)
				{
					case "undo":
						dpanel.gotoPreviousState();
						if(tMenu!=null) tMenu.freePointsRemaining = free_points;
						node = dpanel.appendNewDrawState(node, command_count, free_points, false);
						ShapeProductionTask(parent.parent.parent).saveXML();
						//processCommand("pen", "initiate", this);
						break;
					case "redo":
						dpanel.gotoNextState();
						if(tMenu!=null) tMenu.freePointsRemaining = free_points;
						node = dpanel.appendNewDrawState(node, command_count, free_points, false);
						ShapeProductionTask(parent.parent.parent).saveXML();
						//processCommand("pen", "initiate", this);
						break;
					case "first":
						dpanel.gotoFirstState();
						if(tMenu!=null) tMenu.freePointsRemaining = free_points;
						node = dpanel.appendNewDrawState(node, command_count, free_points, false);
						ShapeProductionTask(parent.parent.parent).saveXML();
						//processCommand("pen", "initiate", this);
						break;
					case "last":
						dpanel.gotoLastState();
						if(tMenu!=null) tMenu.freePointsRemaining = free_points;
						node = dpanel.appendNewDrawState(node, command_count, free_points, false);
						ShapeProductionTask(parent.parent.parent).saveXML();
						//processCommand("pen", "initiate", this);
						break;
				}
			}
			
			this.xml.appendChild(node);
			
			//////////////////////////////////// PROCESS THE COMMAND WITHOUT DRAWSTATE ///////////////////////////////////////////
			
			if (status == "initiate")
			{
				switch (cmd)
				{
					case "toolMenu":
						tMenu = new ToolMenu(192*ShapeProductionTask.unit, this.height, free_points, hideToolArray);
						this.addChild(tMenu);
						processCommand("toolSelection", "initiate", this, new Array("pen"));
						break;
					case "shapeSelectionMenu":
						ssMenu = new ShapeSelectionMenu(192*ShapeProductionTask.unit, this.height, parentPolygons, TYPES);
						this.addChild(ssMenu);
						processCommand("polygonSelection", "initiate", this);
						break;	
					case "polygonSelection":	
						dpanel.shiftModes(cmd);
						currentTool = cmd;
						break;					
					case "pen":
					case "drag":
					case "delete":
					case "pin":
					case "parallel":
					case "length":
					case "angle":
					case "right":
						// make sure that a shift is allowed here
						if (dpanel.shiftModes(cmd))
						{
							if (tMenu != null) tMenu.shiftModes(cmd);
							currentTool = cmd;
						}
						break;
					case "child":
						if (parameters != null)
						{
							type = parameters[0];
							//newMessage(dpanel, "Great job!", child.x+child.width+10, child.y, MessageBox.EPHEMERAL_TYPE);
							getParentPolygonWithType(type).used=true;
							dpanel.clearChild();
							dpanel.setChildType("");
							dpanel.resumeCursor();
							ssMenu.disable = false;
							ssMenu.hideTargetType(type);
							dpanel.saveChildren();
							this.targetMenu.setTypeCheck(type, true);
						}
						break;
				}				
			} else if (status == "in_progress")
			{
				switch (cmd)
				{
					case "toolSelection":
						if (currentTool != "") processCommand(currentTool, "complete", this);
						processCommand(parameters[0], "initiate", this);
						break;
					case "shapeSelection":
						currentShape = parameters[0];
						// if we are growing a new shape disable the shape selection menu
						if (dpanel.setChildType(parameters[0])) ssMenu.disable = true;
						break;
					case "polygonSelection":
						// if we are growing a new shape disable the shape selection menu
						if (dpanel.setChildClosedPolygon(parameters[0])){ ssMenu.disable = true; dpanel.haltCursor(); }						
						break;
					case "pen":	case "delete":	case "drag":case "pin":	case "parallel": case "length":	case "angle": case "right":
						break;					
					case "message":
						if (currentTool != "")  Mouse.hide();
						break;
				}
			} else if (status == "complete")
			{
				switch (cmd)
				{
					case "toolMenu":
						if (!activeObjectives())
						{
							this.removeChild(tMenu);
							tMenu = null;	
							processCommand(currentTool, "complete", this);
							processCommand("shapeSelectionMenu", "initiate", this);
						}	else
						{
							triggerObjectiveOnQueue();
						}					
						break;
					case "shapeSelectionMenu":
						if (!activeObjectives())
						{
							this.removeChild(ssMenu);
							ssMenu = null;	
							processCommand("polygonSelection", "complete", this);
							processCommand("toolMenu", "initiate", this);
						} else
						{
							triggerObjectiveOnQueue();
						}
						break;
					case "polygonSelection":
					case "pen":
					case "drag":
					case "delete":
					case "pin":
					case "parallel":
					case "length":
					case "angle":
					case "right":
						currentTool = "";
						dpanel.completeTool();
						break;
					case "child":
						if (parameters != null)
						{
							type = parameters[0];
							trace("undo child", type, getParentPolygonWithType(type));
							getParentPolygonWithType(type).used=false;
							dpanel.saveChildren();
							if(ssMenu != null) ssMenu.showTargetType(type);
							targetMenu.setTypeCheck(type, false);
						}
						break;
					
				}
			} else if (status == "error")
			{
				switch (cmd)
				{
					case "child":
						error = parameters[0];
						var eid:String = newMessage(dpanel, error , dpanel.x+100, 100, MessageBox.PERSISTENT_TYPE);
						addToActionQueue(String("a"+eid), cmd, status, eid, "child_error",0,0);
						addToObjectiveQueue(String("obj"+eid), "message", "complete", String("a"+eid), 0, 0, true);
						//haltUntilErrorComplete = true;
						break;
				}
			}
			////// any messages to trigger?
			triggerMessageOnQueue(cmd, status, messageID);
			triggerActionOnQueue(cmd, status, messageID);
			disableObjectiveFromQueue(cmd, status, messageID);
			previousCMD = cmd;
			previousStatus = status;
			previousParameters = parameters;
			return true;
		}
		
		/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// KEYBOARD
		private function handleKeyboard(event:KeyboardEvent):void
		{
			/*
			// Delete current point and associated segements
			if(event.keyCode == Keyboard.DELETE || event.keyCode == Keyboard.BACKSPACE)
			{
				processCommand(previousCMD, "complete", this);
				processCommand("delete", "initiate", this);
				
			} else if (event.keyCode == Keyboard.SHIFT)
			{ 
				processCommand(previousCMD, "complete", this);
				processCommand("drag", "initiate", this);
			} 
			else if (event.keyCode == Keyboard.ESCAPE)
			{
				processCommand(previousCMD, "complete", this);
				processCommand("pen", "initiate", this);
			}
			else if (event.keyCode == Keyboard.SPACE)
			{
				processCommand(previousCMD, "complete", this);
				processCommand("pin", "initiate", this);
			}
				////////////////////////////////// PROPERTY TOOLS //////////////////////////////// 
			else if (event.keyCode == Keyboard.P)
			{ 
				processCommand(previousCMD, "complete", this);
				processCommand("parallel", "initiate", this);
			}
			else if (event.keyCode == Keyboard.L)
			{ //////////////// Length ///////////////////////
				processCommand(previousCMD, "complete", this);
				processCommand("length", "initiate", this);
			}
			else if (event.keyCode == Keyboard.A)
			{ //////////////// Parallel ///////////////////////
				processCommand(previousCMD, "complete", this);
				processCommand("angle", "initiate", this);
			}
			else if (event.keyCode == Keyboard.R)
			{ //////////////// Parallel ///////////////////////
				processCommand(previousCMD, "complete", this);
				processCommand("right", "initiate", this);
			} else if (event.keyCode == Keyboard.Z && event.ctrlKey)
			{ // UNDO
				processCommand(previousCMD, "complete", this);
				processCommand("undo", "initiate", this);
			}else if (event.keyCode == Keyboard.Y && event.ctrlKey)
			{ // UNDO
				processCommand(previousCMD, "complete", this);
				processCommand("redo", "initiate", this);
			}
			*/
		}
	}
}